A global variable can be modified from anywhere in the program. At first, this might look convenient. However, it makes programs harder to
understand and maintain. When you see a function call, you cannot know if the function will affect the value of the global variable or not. You have
lost the ability to reason locally about your code and must always have the whole program in mind.
Additionally, global variables are often subject to race conditions in multi-threaded environments.
These issues are related to modification and cannot occur when the global variable is const
(or, in the case of a pointer, if it is
const
at every level).
unsigned** noncompliantPtr;
unsigned const* const* const compliantPtr = ...;
Some global variables defined in external libraries (such as std::cout
, std::cin
, std::cerr
) are acceptable
to use, but you should have a good reason to create your own. If you use a global variable, ensure they can be safely accessed concurrently.
Remember that it is much easier to maintain software without globals. Instead of such variables, it is better to design functions to take as input
all the required variables. In addition to serving documentation, this also helps future refactoring and the evolution of the code.
This rule detects all declarations of global variables (at file scope or in any namespace) that are not constant.
Noncompliant code example
double oneFoot = 0.3048; // Noncompliant
double userValue; // Noncompliant
void readValue();
void writeResult();
int main() {
readValue();
writeResult();
}
Compliant solution
constexpr double footToMeter = 0.3048;
double readValueInFeet();
void writeResult(double valueInMeters);
int main() {
auto userValue = readValueInFeet();
writeResult(userValue * footToMeter);
}
Exceptions
volatile
is used to indicate that some piece of memory can be mutated by external factors. For embedded software, some hardware
inputs/outputs can be mapped to specific memory addresses, and accessing these bound data is usually done through a global pointer to
volatile
data.
In that situation, the pointer itself should be const, but the pointee can be non-const if the memory maps an output register that is supposed to
be written to.
unsigned volatile * gpio1; // Noncompliant
unsigned volatile * const gpio2 = ...; // Compliant, used for input & output
unsigned volatile const* const gpio3 = ...; // Compliant, used for input only